﻿
/// <reference path="../DomElements.js" />

/*****************************************************************************
Ajax library. Kevin Robinson 2006.
Bootstrapper (1 of 2) file containing language extensions. 
/*****************************************************************************/

// Namespace
//$addNamespace("ajax.lang");    // Technically correct
//$addNamespace("ajax.debug");
var ajax = {};                  // Doing it this way enables some intellisense IN THIS source file
ajax.lang = {};
ajax.debug = {};
ajax.version = "1.1.3";
ajax.__namespace = true;


// Changes
/*****************************************************************************
1.1.3// Sept 08
Changed ajax.drawing.Colour class (cleaned and optimized) and ajax.components.ColorPicker.

/*****************************************************************************
1.1.2// Aug 08
Added some Enum Support. use $extendEnum() to declare enums. Enums MUST be $import'ed before use.
Added String.toFormat (static and instance), to provide similar support for String.Format.

/*****************************************************************************
1.1.1// Jun 08
Changed .parse to .cast (in line with MSAjaxExtensions).
Some class loading changes for .domain - to allow for a gateway to be specified.
Added js.js - a mirror of the References.js file but contains the assembly refs instead of local paths
(so that external clients can download this and include it in their local _namespace.js files.
Oh and this Project is now an ASP.NET Server control!

/*****************************************************************************
1.1.0// Mar 08
More Intellisense support.
Added ajax.xna!! This is an experimental work in progress to approximate the MS XNA API in a web browser using javascript.

Added $ sign to all global shortcut methods like addNamespace and addApplication (IMPORTANT: this is a code-breaker!!).

Now enforcing an ID for every container. We don't need id's on DOM elements, and if we want to create multiple versions of a container class
then passing a unique ID will make that not possible (as the original version will be overwritten instead on the DOM).
So now, if passed a blank string, the ajax.components.Container class will generate a Guid and use that as the layer.id value. 
This is to workaround issues with certain browsers (Opera mostly but IE too sometimes), where a lack of id 
(my hunch is that appending an inner element with id="" to an outer with id="") caused the outer element to seemingly remove itself from the DOM...not good!

1.0.6 // Jan 08
Sundry changes to add Intellisense support. Currently a script must <reference> the References.js
script (in the root of ajax) to enable intellisense in VS 2008.
Added shortcut $ methods:
$getEvent -> ajax.lang.getEvent
$new      -> ajax.lang.safeNew
$call     -> ajax.lang.safeCall
$imports  -> ajax.lang.imports
$extends  -> ajax.lang.extends
Also added fillArc method to graphics (now we can do pie charts!!)
/*****************************************************************************
1.0.5 // Sept 07
Added support for MDI WinForms. Just call .addMdiChild(form) to a parent and set the parents content as the ParentNode.
Added "data" namespace and DataTable object.
Removed un-needed "window" statements from this extensions file (to enable server side use).
Removed reliance on window in addNamespace method (to enable server side use).
Changed the client app namespace to be just "app" to avoid clash with server side ASP "Application"
This version can now be included within a Server Side Javascript Library. Even Dynamic loading is working (see ajax.domain).
Also server clients can also use Server.MapPath to set the ajax & app domains (eg: ajax.domain = Server.MapPath("/") + "/";).
Also introduced ajax.lang.getType.
Also created AjaxComponent, currently supports a default .progressIndicator (Container)
/*****************************************************************************
1.0.4 
Treeview.js updated to support "requestParams" array property. These are passed on to the ContentLoader(s)
used by the treeview and facilitates passing more than just the key.
/*****************************************************************************
1.0.3
ColorPicker.js class added.
/*****************************************************************************
1.0.2
DatePicker.js .setPosition method to use getElementLayerOffset not getElementPosition.

/*****************************************************************************
1.0.1
Object Methods: Removed all object methods as although these don't clash with MS Ajax, MS Ajax has
reflection code that will baulk if it sees "properties of Object that are not numeric".

Any code that uses these object methods will now obviously break. I've run a check and no engine code does 
(it was a convienence for the client after all). The functionality offered can still be used, you must simply
pass the object in question to any of the following methods, ie:

ajax.lang.toJsonString(this, bIncludeFunctions);
ajax.lang.toXmlObject(this, bIncludeFunctions);
ajax.lang.toXmlString(this, bIncludeFunctions);
ajax.lang.getProperties(this);


/*****************************************************************************/

//-----------------------------------------------------------------------------
// ** Array stuff from Peter Belisis
if(Array.prototype.push && ([0].push(true)==true))
    Array.prototype.push = null;
if(Array.prototype.splice && typeof([0].splice(0))=="number")
    Array.prototype.splice = null;

if(!Array.prototype.shift) {
	function array_shift() {
		firstElement = this[0];
		this.reverse();
		this.length = Math.max(this.length-1,0);
		this.reverse();
		return firstElement;
	}
	Array.prototype.shift = array_shift;
}

if(!Array.prototype.unshift) {
	function array_unshift() {
		this.reverse();
		for(var i=arguments.length-1;i>=0;i--){
			this[this.length]=arguments[i]
		}
		this.reverse();
		return this.length
	}
	Array.prototype.unshift = array_unshift;
}

if(!Array.prototype.push) {
	function array_push() {
		for(var i=0;i<arguments.length;i++){
			this[this.length]=arguments[i]
		};
		return this.length;
	}
	Array.prototype.push = array_push;
}

if(!Array.prototype.pop) {
	function array_pop(){
	    lastElement = this[this.length-1];
		this.length = Math.max(this.length-1,0);
	    return lastElement;
	}

	Array.prototype.pop = array_pop;
}

if(!Array.prototype.splice) {
    function array_splice(ind,cnt){
        if(arguments.length == 0) return ind;
        if(typeof ind != "number") ind = 0;
        if(ind < 0) ind = Math.max(0,this.length + ind);
        if(ind > this.length) {
            if(arguments.length > 2) ind = this.length;
            else return [];
        }
        if(arguments.length < 2) cnt = this.length-ind;
        cnt = (typeof cnt == "number") ? Math.max(0,cnt) : 0;
        removeArray = this.slice(ind,ind+cnt);
        endArray = this.slice(ind+cnt);
        this.length = ind;
        for(var i=2;i<arguments.length;i++){
            this[this.length] = arguments[i];
        }
        for(var i=0;i<endArray.length;i++){
            this[this.length] = endArray[i];
        }
        return removeArray;
    }
	Array.prototype.splice = array_splice;
}

if (!Array.prototype.roll)
{
	function array_roll (direction) {
		
		direction = direction || false
		if ( !direction )
		{
			this.splice(this.length, 1, this[0])
			this.splice(0,1)
		} else
		{
			this.splice(0,0, this[this.length-1])
			this.splice(this.length-1,1)
		}
	}
	Array.prototype.roll = array_roll; 
}

if (!Array.prototype.copy)
{
    Array.prototype.copy = function()
    {
        var newArray = [];
        for (var i = 0; i < this.length; i++)
        {
            newArray[i] = this[i];
        }
        return newArray;
    }
}

//-----------------------------------------------------------------------------
// ** Array stuff from Dave Crane
/*
append to end of array, optionally checking for duplicates
*/
Array.prototype.append=function(obj,nodup){
  if (!(nodup && this.contains(obj))){
    this[this.length]=obj;
  }
}

/*
return index of element in the array
*/
Array.prototype.indexOf=function(obj){
  var result=-1;
  for (var i=0;i<this.length;i++){
    if (this[i]==obj){
      result=i;
      break;
    }
  }
  return result;
}

/*
return true if element is in the array
*/
Array.prototype.contains=function(obj){
  return (this.indexOf(obj)>=0);
}

/*
empty the array
*/
Array.prototype.clear=function(){
  this.length=0;
}

/*
insert element at given position in the array, bumping all
subsequent members up one index
*/
Array.prototype.insertAt=function(index,obj){
  this.splice(index,0,obj);
}

/*
remove element at given index
*/
Array.prototype.removeAt=function(index){
  this.splice(index,1);
}

/*
return index of element in the array
*/
Array.prototype.remove=function(obj){
  var index=this.indexOf(obj);
  if (index>=0){
    this.removeAt(index);
  }
}

//-----------------------------------------------------------------------------
// Object JSON stuff from Kevin Robinson
/*
Object.prototype.toJsonString = function(bIncludeFunctions)
{
    return ajax.lang.toJsonString(this, bIncludeFunctions);
}
Object.prototype.toXmlObject = function(bIncludeFunctions)
{
    return ajax.lang.toXmlObject(this, bIncludeFunctions);
}
Object.prototype.toXmlString = function(bIncludeFunctions)
{
    return ajax.lang.toXmlString(this, bIncludeFunctions);
}
Object.prototype.getProperties = function()
{
    return ajax.lang.getProperties(this);
}
*/
//-----------------------------------------------------------------------------
// String JSON stuff from Kevin Robinson
String.prototype.toJsonObject = function()
{
    return ajax.lang.toJsonObject(this);
}
String.prototype.xmlToJsonString = function()
{
    return ajax.lang.xmlToJsonString(this);
}
String.prototype.xmlToJsonObject = function()
{
    return ajax.lang.xmlToJsonObject(this);
}
String.prototype.isNumeric = function()
{
    return ajax.lang.isNumeric(this);
}
String.prototype.htmlEncode = function(display, tabs)
{
    return ajax.lang.htmlEncode(this, display, tabs);
}
String.prototype.trim = function()
{
    return ajax.lang.trim(this);
}

String.toFormat = function(formatString, paramArgs)
{
    var args = arguments;

    var result = '';
    var format = args[0];
    for (var i = 0; ; )
    {
        var open = format.indexOf('{', i);
        var close = format.indexOf('}', i);
        if ((open < 0) && (close < 0))
        {
            result += format.slice(i);
            break;
        }
        if ((close > 0) && ((close < open) || (open < 0)))
        {
            if (format.charAt(close + 1) !== '}')
            {
                throw new Error("format", "Format Brace Mismatch");
            }
            result += format.slice(i, close + 1);
            i = close + 2;
            continue;
        }
        result += format.slice(i, open);
        i = open + 1;
        if (format.charAt(i) === '{')
        {
            result += '{';
            i++;
            continue;
        }
        if (close < 0) throw new Error("format", "Format Brace Mismatch");
        var brace = format.substring(i, close);
        var colonIndex = brace.indexOf(':');
        var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10) + 1;
        if (isNaN(argNumber)) throw new Error("format", "Format Invalid");
        var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1);
        var arg = args[argNumber];
        if (typeof (arg) === "undefined" || arg === null)
        {
            arg = '';
        }
        else
            result += arg.toString();
        i = close + 1;
    }
    return result;
}

String.prototype.toFormat = function(paramArgs)
{
    return String.toFormat(this, paramArgs);
}


//-----------------------------------------------------------------------------
// Bind method for Functions (from an idea in the Prototype engine).
// This will return a function pointer with a context of that passed,
// Making it perfect for event handler pointers.
Function.prototype.bind = function(obj)
{///<returns type="Function"/>    
    var method = this;
    var temp   = function()
    {
        return method.apply(obj, arguments);
    };
    return temp;
}

Function.createBinding = function(context, func)
{
    return func.bind(context);
}

$ = function(doc, sId)
{///<returns type="domElement" domElement="true"/>
    return doc.getElementById(sId);
}

/*****************************************************************************
This block defines __typeName and __class (mimicking MS AJAX). 
/*****************************************************************************/
Function.__typeName = "Function";   Function.__class    = true;
Object.__typeName   = "Object";     Object.__class      = true;
String.__typeName   = "String";     String.__class      = true;
Array.__typeName    = "Array";      Array.__class       = true;
RegExp.__typeName   = "RegExp";     RegExp.__class      = true;
Error.__typeName    = "Error";      Error.__class       = true;

Object.getConstructor = function(instance)
{   ///<summary>Mimicks MSAJAX Object.getType</summary>
    ///<returns type="Function"/>
    var ctor = instance.constructor;
    if (!ctor || (typeof (ctor) !== "function") || !ctor.__typeName || (ctor.__typeName === "Object"))
        return Object;
    return ctor;
}

Function.prototype.getFunctionName = function()
{   ///<summary>Mimicks MSAJAX Type.getName (or Function.getName since Type = Function).</summary>
    ///<returns type="String"/>
    return (typeof (this.__typeName) === "undefined") ? "" : this.__typeName;
}

Object.getConstructorName = function(instance)
{   ///<summary>Mimicks MSAJAX Object.getTypeName</summary>
    ///<returns type="String"/>
    return Object.getConstructor(instance).getFunctionName();
}
/*****************************************************************************/


//-----------------------------------------------------------------------------
// ########## Start of Ajax Lang Static methods
ajax.path           = "ajax";
ajax.domain         = ""; // leave this blank for default. When using this library on the server side, this can be filled in, eg "http://www.site.com/"
ajax.registeredApps = [];
// register a default application
$addApplication("app", "");

// Ajax can maintain a list of installed components
ajax.Items = [];  // hash table



// Some browser detection. Watch out if running on the server (no navigator).
ajax.Browser = {};
if (typeof(navigator) == "undefined") navigator = {userAgent: "MSIE"};
ajax.Browser.navigator          = navigator;
ajax.Browser.isInternetExplorer = navigator.userAgent.indexOf("MSIE")   != -1;
ajax.Browser.isFirefox          = navigator.userAgent.indexOf("Gecko")  != -1 && navigator.userAgent.indexOf("KHTML") == -1;
ajax.Browser.isSafari           = navigator.userAgent.indexOf("Safari") != -1;
ajax.Browser.isOpera            = navigator.userAgent.indexOf("Opera")  != -1;
ajax.Browser.isGoogleChrome     = navigator.userAgent.indexOf("KHTML")  != -1;
ajax.Browser.userAgent          = navigator.userAgent;
ajax.Browser.version            = parseInt(navigator.appVersion);
ajax.Browser.hasCanvas          = document ? (document.createElement("canvas").getContext ? true : false) : false;

ajax.lang.isNumeric = function(sText)
{    
    var validChars = "0123456789.";
    var isNumber=true;
    var char;

    for (var i = 0; i < sText.length && isNumber == true; i++) 
    { 
        char = sText.charAt(i); 
        if (validChars.indexOf(char) == -1)        
            isNumber = false;         
    }
    return isNumber;
}

//-----------------------------------------------------------------------------
ajax.lang.getType = function(obj)
{///<summary>Returns the string representing the basic type of the object passed (string, date, array, numeric, etc)</summary>
 ///<returns type="string"/>
    // special case for Array and date, otherwise use typeof
    var t   = "null";
    try
    {
        t   = (obj.constructor == Array) ? "array" : typeof(obj); 
        t       = (obj.constructor == Date) ? "date" : t;
    }
    catch(ex){}
    return t;
}

//-----------------------------------------------------------------------------
// Helper function to return a valid new XmlDocDom
ajax.lang.createXmlDoc = function()
{
    var xmlDoc = null;
    if (document.implementation && document.implementation.createDocument)
	    xmlDoc = document.implementation.createDocument("", "", null);	        
    else if (ActiveXObject)    
	    xmlDoc = new ActiveXObject("Microsoft.XMLDOM");   	    
	return xmlDoc;
}

//-----------------------------------------------------------------------------
ajax.lang.loadXmlDoc = function(sXml)
{
    var xmlDoc = null;
    var w3c = false;
    try
    {
        w3c = document && document.implementation && document.implementation.createDocument;
    } catch(ex){}
    
    if (w3c)
    {
        //create a DOMParser
        var objDOMParser = new DOMParser();
            
        //create new document from string
        var xmlDoc = objDOMParser.parseFromString(sXml, "text/xml");
    }        	            
    else if (ActiveXObject)    
    {
	    xmlDoc = new ActiveXObject("Microsoft.XMLDOM");   	    
	    xmlDoc.loadXML(sXml);
	}	
	return xmlDoc;
}

//-----------------------------------------------------------------------------
// Helper function to return an xml Header if needed
ajax.lang.getXmlHeader = function(sEncoding)
{
    var xmlHeader = "<?xml version=\"1.0\"";
        
    if (sEncoding)
        xmlHeader += " encoding=\"" + sEncoding + "\"";
    
    xmlHeader += "?>\n\r";    
    return xmlHeader;    
}

//-----------------------------------------------------------------------------
ajax.lang.getXmlInnerText = function(node) 
{    
    if (typeof node.textContent != 'undefined') 
    {
        return node.textContent;
    }
    else if (typeof node.innerText != 'undefined')
    {        
        return node.innerText;
    }
    else if (typeof node.text != 'undefined') 
    {
        return node.text;
    }
    else
    {
        switch (node.nodeType) 
        {
            case 3:
            case 4:
                return node.nodeValue;
                break;
            case 1:
            case 11:
                var innerText = '';
                for (var i = 0; i < node.childNodes.length; i++) 
                {
                    innerText += getInnerText(node.childNodes[i]);
                }
                return innerText;
                break;
            default:
                return '';
        }
    }    
}

//-----------------------------------------------------------------------------
// Takes an xmlDom node and returns the json string from it.
// REPLACED
ajax.lang.xmlNodeToJson = function(node)
{    
    var json = "";
    var name = node.nodeName;
    
    if (name == "#text") 
    {
        /// WTF?? some firefox crap...
        return "";
    }
            
    var bArray = (node.getAttribute("type") == "array");
    var beginObj = (bArray) ? "[\n\r" : "{\n\r";
    var endObj   = (bArray) ? "\n\r]" : "\n\r}";
    
    // We can't convert an xml header node to json
    if (name == "xml")
        return "";
    
    // Get the "type" of the node (if it has one)
    var nodeType = node.getAttribute("type");        

    if (nodeType == "object" || nodeType == "array")
    {                   
        // This has sub elements so recurse through again
        if (name != "object")
            json += name + ": ";
            
        json += beginObj;
            
        for (var i = 0; i < node.childNodes.length; i++)
            json += ajax.lang.xmlNodeToJson(node.childNodes[i]);
        
        json += endObj;
    }        
    else if (nodeType == "date")
    {
        if (name != "object")
            json += name + ": ";
        
        json += "\"" + node.text + "\"";
    }
    else if (nodeType == "string")    
    {
        // Deal with quote marks in the string...
        var txtVal = ajax.lang.getXmlInnerText(node);        
        
        // We must be very carefull about newline characters in any string - we must double escape 
        txtVal = txtVal.replace(/\r/gi, "\\r");
        txtVal = txtVal.replace(/\n/gi, "\\n");
        txtVal = txtVal.replace(/\"/gi, '\\"');
        
        if (name != "object")
            json += name + ": ";
        
        json += "\"" + txtVal + "\"";
    }
    else if (nodeType == "function" && node.parentNode.getAttribute("type") == "array")
        // Oh dear..
        return "";
    else
    {
        if (name != "object")
            json += name + ": ";
        json += ajax.lang.getXmlInnerText(node);
    }           
    
    if (node.nextSibling != null)
    {                      
        // This can cause problems (see below)
        json += ",";
    }
    
    // Due to problems in browser XmlDom implemetations (of .nextSibling, Opera at least), we can end up with commas we don't want
    // So we will get rid of the ones that are obviously not supposed to be there (end of array and end of object).
    json = json.replace(/,\n\r]/gi, "\n\r]");
    json = json.replace(/,\n\r}/gi, "\n\r}");
    
    return json;            
}

//-----------------------------------------------------------------------------
// Takes an xml Doc DOM and return the json string from it.
ajax.lang.xmlDocToJson = function(xmlDoc)
{      
    var sItems = "";    
    for (var i = 0; i < xmlDoc.childNodes.length; i++)
    {
        var node = xmlDoc.childNodes[i];
        
        if (sItems != "") 
        {                     
            sItems += ",";      
        }  
  
        sItems += ajax.lang.xmlNodeToJson(node);

    }    
    return sItems;
}

//-----------------------------------------------------------------------------
// (String prototype) Takes an xml string and returns the json string from it.
ajax.lang.xmlToJsonString = function(string)
{
    var xmlDoc = ajax.lang.loadXmlDoc(string);
    return ajax.lang.xmlDocToJson(xmlDoc);    
}

//-----------------------------------------------------------------------------
// (String prototype) Takes an xml string and converts it to a javascript object
ajax.lang.xmlToJsonObject = function(string)
{
    // Convert the xml string into json
    var json = ajax.lang.xmlToJsonString(string);
    
    var obj = null;
    try
    {
        obj = eval("(" + json + ")");
    }
    catch(ex) 
    {        
        alert("Error parsing JSON = " + json);
    }
    return obj;
}

//-----------------------------------------------------------------------------
// (OBJECT prototype) Takes a javascript object and converts it to an xml Doc DOM object
ajax.lang.toXmlObject = function(obj, bIncludeFunctions)
{
    var xmlString = ajax.lang.toXmlString(obj, bIncludeFunctions);
    var xmlDoc = ajax.lang.loadXmlDoc(xmlString);    
    return xmlDoc;
}

//-----------------------------------------------------------------------------
// (OBJECT prototype) Takes a javascript object and converts it to an xml string
ajax.lang.toXmlString  = function(obj, bIncludeFunctions, sName)
{
    bIncludeFunctions = (typeof(bIncludeFunctions) != "boolean") ? false : bIncludeFunctions;    
    sName = (typeof(sName) == "undefined" || sName == null) ? "object" : sName
    var objType = (obj.constructor == Array) ? "array" : typeof(obj);    
    
    // We kow its an object - just what TYPE.
    var beginTag = "<" + sName + " type=\"" + objType + "\">\n\r";
    var endTag   = "\n\r</" + sName + ">";
        
    var sItems = "";
    
    // Loop through all properties of this object
    for (var prop in obj)
    {
        var type    = (obj[prop].constructor == Array) ? "array" : typeof(obj[prop]); 
        type        = (obj[prop].constructor == Date) ? "date" : type;
                     
        var tab     = "    ";
        var tagName = prop;
        
        // if the type is a number, then it's part of an array so must be object.
        if (tagName.isNumeric())
            tagName = "object";
                    
        // Skip Functions?
        if (type == "function" && !bIncludeFunctions)
            continue;                    
        
        if (type == "string")
        {
            var s = obj[prop];
            
            // Deal with the nasty chars in the string...            
            s = s.replace(/\&/ig, "&amp;");
            s = s.replace(/\</ig, "&lt;");
            s = s.replace(/\>/ig, "&gt;");            
            s = s.replace(/\'/ig, "&apos;");
            s = s.replace(/\"/ig, "&quot;");            
                       
            sItems += tab + "<" + tagName + " type=\"" + type + "\">" + s + "</" + tagName + ">\n\r";
        }        
        else if (type == "object")                
                sItems += ajax.lang.toXmlString(obj[prop], bIncludeFunctions, tagName);        
        else if (type == "function")
        {
            // Function objects need to be CDATA         
            sItems += tab + "<" + tagName + " type=\"" + type + "\"><![CDATA[" + obj[prop] + "]]>" + "</" + tagName + ">\n\r";
        }   
        else
            // This is numeric or boolean etc...
            sItems += tab + "<" + tagName + " type=\"" + type + "\">" + obj[prop] + "</" + tagName + ">\n\r";           
    }
    var xml = beginTag + sItems + endTag;
    return xml;
}

//-----------------------------------------------------------------------------
// (Object Prototype) takes a javascript objects and returns a JSON string
ajax.lang.toJsonString = function(obj, bIncludeFunctions, bIncludeObjects)
{    
    bIncludeFunctions = (typeof(bIncludeFunctions) != "boolean") ? false : bIncludeFunctions; // default false
    bIncludeObjects   = (typeof(bIncludeObjects) != "boolean") ? true : bIncludeObjects; // default true
    var json = "";
    var bArray = (obj && obj.constructor == Array);
    var bDate  = (obj && obj.constructor == Date);
    var beginTag = (bArray) ? "[\n\r" : "{\n\r";
    var endTag   = (bArray) ? "\n\r]" : "\n\r}";
    var sItems   = "";
    for (var prop in obj)
    {
        var type = typeof(obj[prop])
        
        // Skip functons if told to or if we have an array?
        if (type == "function")
            if (!bIncludeFunctions || bArray)
                continue;
        
        if (sItems != "")
            sItems += ",\n\r";
        
        if (!bArray)
            sItems += prop + ": ";
        
        if (obj[prop] && obj[prop].constructor == Date)
            sItems += "\"" + obj[prop] + "\"";
        else if (type == "string")
        {
            // Deal with quote marks in the string...
            var txtVal =  obj[prop];
            
            // We must be very carefull about newline characters in any string - we must double escape 
            txtVal = txtVal.replace(/\r/gi, "\\r");
            txtVal = txtVal.replace(/\n/gi, "\\n");
            txtVal = txtVal.replace(/\"/gi, '\\"');
            
            sItems += "\"" + txtVal + "\"";
            
        }
        else if (type == "object")     
        {       
            if (bIncludeObjects)
            {
                // Could be a non ajax object so use the generic json creator
                sItems += ajax.lang.toJsonString(obj[prop], bIncludeFunctions);
            }
            else
            {
                sItems += "null";
            }
        }
        else
            sItems += obj[prop];
    }
    json = beginTag + sItems + endTag; 
    
    return json;        
}

//-----------------------------------------------------------------------------
// (String Prototype) takes a JSON string and converts to a javascript object.
ajax.lang.toJsonObject = function(string)
{
    var ret = null;
    try
    {
        ret = eval("(" + string.toString() + ")");
    }catch(ex){}        
    return ret;   
}

//-----------------------------------------------------------------------------
//(String)  source   The text to be encoded.
//(boolean) display   The output is intended for display.
//(integer) tabs      The number of spaces to expand tabs to. 
ajax.lang.htmlEncode = function(source, display, tabs)
{
    // Only do this for strings and dates, otherwise return the original
    var sourceType = ajax.lang.getType(source);    
    if (sourceType != "string" && sourceType != "date")
        return source;
        
	function special(source)
	{
		var result = '';
		for (var i = 0; i < source.length; i++)
		{
			var c = source.charAt(i);
			if (c < ' ' || c > '~')
			{
				c = '&#' + c.charCodeAt() + ';';
			}
			result += c;
		}
		return result;
	}
	
	function format(source)
	{
		// Use only integer part of tabs, and default to 4
		tabs = (tabs >= 0) ? Math.floor(tabs) : 4;
		
		// split along line breaks
		var lines = source.split(/\r\n|\r|\n/);
		
		// expand tabs
		for (var i = 0; i < lines.length; i++)
		{
			var line = lines[i];
			var newLine = '';
			for (var p = 0; p < line.length; p++)
			{
				var c = line.charAt(p);
				if (c === '\t')
				{
					var spaces = tabs - (newLine.length % tabs);
					for (var s = 0; s < spaces; s++)
					{
						newLine += ' ';
					}
				}
				else
				{
					newLine += c;
				}
			}
			// If a line starts or ends with a space, it evaporates in html
			// unless it's an nbsp.
			newLine = newLine.replace(/(^ )|( $)/g, '&nbsp;');
			lines[i] = newLine;
		}
		
		// re-join lines
		var result = lines.join('<br />');
		
		// break up contiguous blocks of spaces with non-breaking spaces
		result = result.replace(/  /g, ' &nbsp;');
		
		// tada!
		return result;
	}

	var result = source;
	
	// ampersands (&)
	result = result.replace(/\&/g,'&amp;');

	// less-thans (<)
	result = result.replace(/\</g,'&lt;');

	// greater-thans (>)
	result = result.replace(/\>/g,'&gt;');
	
	if (display)
	{
		// format for display
		result = format(result);
	}
	else
	{
		// Replace quotes if it isn't for display,
		// since it's probably going in an html attribute.
		result = result.replace(new RegExp('"','g'), '&quot;');
	}

	// special characters
	result = special(result);
	
	// tada!
	return result;
}

//-----------------------------------------------------------------------------
ajax.lang.trim = function(str)
{
    // Force str to become a string...then use string replace function
    return ("" + str + "").replace(/^\s+|\s+$/g, '');   
}







//-----------------------------------------------------------------------------
ajax.debug.defaultOutput    = 
{
    writeLn: function(s){this.log += s + "\n"},
    clear: function(){this.log = ""},
    log: ""
};
ajax.debug.Output    = ajax.debug.defaultOutput;
ajax.debug.writeToNewWindow = function(sText)
{
    try
    {
        var win = window.open("about:blank");
        win.document.open();
        win.document.write(sText);
        win.document.close();
    }
    catch(ex)
    {
        alert("Unable to open window (blocked?). " + ex.message);
    }
    
}

//-----------------------------------------------------------------------------
ajax.lang.safeDestroy = function(ele)
{
    if (ele)
        if (ele.outerHTML)
		    ele.outerHTML = "";
		else if (ele.parentNode)
            ele.parentNode.removeChild(ele);        
	ele = null;	
	return ele;	
};


//-----------------------------------------------------------------------------
// This helper method gets the path to the Class File from the class name.
// It uses a rule, that the first UPPER case letter denotes the class file.
ajax.lang.getClassPath = function(sClassName, sExt, sDelimeter)
{
    sExt = (typeof (sExt) == "undefined") ? ".js" : sExt;
    sDelimiter = (typeof (sDelimiter) == "undefined") ? "/" : sDelimiter;

    var arr = sClassName.split('.');
    var sPath = "";
    for (var i = 0; i < arr.length; i++)
    {
        var sName = arr[i];
        var firstLetter = arr[i].substring(0, 1);

        if (sPath != "")
            sPath += sDelimiter;

        // Check for first element and transform for domain and path.        
        if (i == 0)
        {
            if (sName == "ajax")            
                sPath = ajax.domain + ajax.path;                
            
            else if (ajax.registeredApps.contains(sName))
                sPath = eval(sName).domain + eval(sName).path;                
                            
            else
                sPath = sName;
        }
        else
            sPath += sName;


        /*
        if (sName == "ajax")
        sPath += ajax.domain + ajax.path;
        else if (ajax.registeredApps.contains(sName))
        sPath += eval(sName).path;
        else
        sPath += sName;
        */

        /*
        if (sName == "ajax")
        sPath += ajax.path;
        else if (sName == "app")
        sPath += app.path;
        else
        sPath += sName; 
        */

        // If this is the first part with a capital letter at the start
        // Then THIS is the class file to return
        if (firstLetter == firstLetter.toUpperCase())
            break;
    }
    // Add the js at the end
    sPath += sExt;
    return sPath;
};


//-----------------------------------------------------------------------------
ajax.lang.getClassName = function(sName)
{
    return sName.replace(/\./ig, "-");
}

//-----------------------------------------------------------------------------
ajax.lang.safeNew = function(stringClassName, params)
{///<summary>Creates an instance of any class. Use as factory, then call .cast (for intellisense) for dynamic class loading.</summary>
    
    // Copy arguments into one of our new extended Arrays
    var args = new Array();
    for (var i = 0; i < arguments.length; i++)    
        args[i] = arguments[i];
    args.shift();  
        
    var className = arguments[0];
    // Ensure that the ClassName exists.
    ajax.lang.imports(className);
    var classObject = ajax.lang.toJsonObject(className);// eval(className);
    // Return a new instance of what they wanted, allowing for 16 arguments!!! Needs work?
    // to be passed to  the constuctor    
    return new classObject(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]);
};
var $new = ajax.lang.safeNew;

//-----------------------------------------------------------------------------
ajax.lang.safeCall = function(stringClassName, stringMethodName, params)
{///<summary>Calls a static class method</summary>
    var args = new Array();
    for (var i = 0; i < arguments.length; i++)
        args[i] = arguments[i];
    args.shift();
    args.shift();
    
    var className = arguments[0];
    var methodName= arguments[1];
    
    ajax.lang.imports(className);
    var classObject = eval(className);
    // Return the result of calling the method in question.
    return classObject[methodName](args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]);
};
var $call = ajax.lang.safeCall;

//-----------------------------------------------------------------------------
ajax.lang.imports = function(classNamesParams)
{
    // for clients wanting to know the results for each className passed in
    // we will tell them true (1) or false (0) depending on if it ends up in existance
    var results = new Array();
    
    for (var i = 0; i < arguments.length; i++)
    {
        // Does this object definition exist?
        var className   = arguments[i];
        var classObject = null;
        try
        {
            classObject = eval(className);
        }
        catch(ex){}
        
        // If not then we must attempt to load it
        if (classObject == null || typeof(classObject) == "undefined")
        {        
            // Does not (yet) exist
            var classPath = ajax.lang.getClassPath(className);
            try
            {
                ajax.debug.Output.writeLn("Class " + className + " does not yet exist\nloading it now from " + classPath);
                // Load it sync fashion
                var cl = new ajax.net.ContentLoader(this, function(){}, null, classPath, "GET", null, false);
                var req = cl.sendRequest();
                // Execute the package                
                classObject = eval(req.responseText);
                ajax.debug.Output.writeLn(className + " loaded OK.\n");
                results[i] = true;
            }
            catch(ex)
            {
                results[i] = false;
                var sErr = "";
                for (var j in ex)
                    sErr += j + ": " + ex[j] + "\n";
                ajax.debug.Output.writeLn("Unable to load " + className + " from " + ajax.domain + classPath + " because\n\n" + sErr);
            }
        }
        else
            // Exists already
            results[i] = true;     
                          
    }
    // return the array to the client, all SHOULD be true
    return results;
};
var $imports = ajax.lang.imports;


//

//-----------------------------------------------------------------------------
ajax.lang.extend = function(subClassName, baseClassName)
{///<summary>This allows a class to be registered or INHERIT from another base class.</summary>
 ///<param name="subClassName" type="String">The fully qualified string name of your new class or interface.</param>
 ///<param name="baseClassName" type="String">Optional. The fully qualified name of the class you wish to inherit from.</param>
         
    // Check clients are using the new way of passing a string in.
    if (typeof(subClassName) != "string")
        ajax.debug.Output.writeLn(String.toFormat("ajax.lang.extend: Error: {0} is not a string", subClassName));
        
    // Ensure that the base class exists (if specified)
    var baseClass = null;    
    if (baseClassName)
    {
        ajax.lang.imports(baseClassName);        
        baseClass = eval(baseClassName);
    }
    
    // And eval the subClassName to get the subclass type..
    var subClass = eval(subClassName);
    
    if (baseClass && baseClass.__enum)
    { 
        // No inheritance for enums.
    }
    else
    {    
        // Create a "dummy" class to avoid calling the base
        // Class constructor with NO Params!
        function inheritance(){};
        // If baseType is specified, set our prototype to use it.
        if (baseClass)
            inheritance.prototype           = baseClass.prototype;
        
        // We can now call the dummy - no problem        
        subClass.prototype              = new inheritance();
      
        // And specify its constructor to avoid confusion
        subClass.prototype.constructor  = subClass;
        // in line with msajax
        subClass.__typeName             = subClassName;
        subClass.__class                = true; 
        subClass.__namespace            = false; // incase this was previously defined as a namespace?
        
        // If the base class exists, store away refrences to the base constructor and the base class prototype itself
        if (baseClass)
        {
            subClass.superConstructor       = baseClass;
            subClass.superClass             = baseClass.prototype;
        }
    }
}
var $extend = ajax.lang.extend;

//-----------------------------------------------------------------------------
ajax.lang.extendEnum = function(subEnumName, isFlags)
{///<param name="subEnumName" type="String">The fully qualified string name of the new enum</param>
 ///<param name="isFlags" type="bool">Optional parameter. Pass true if the enum is flag-able (currently makes no difference).</param>
    var subEnum = eval(subEnumName);
    
    subEnum.__enum = true;
    subEnum.__flags = isFlags || false;
    subEnum.__typeName = subEnumName;
    
    for (var i in subEnum.prototype)
        subEnum[i] = subEnum.prototype[i];
    
}
var $extendEnum = ajax.lang.extendEnum;


//-----------------------------------------------------------------------------
   
ajax.lang.isInstanceOf = function(obj, className)
{///<summary>Method to determine if one class is of a partucular type.</summary>
 ///<returns type="Boolean">Returns true if the object passed INHERITS from the className passed.</returns>
 
    if (!obj)
        return false;
        
    // Evaluate the className we are to compare to into a class definition
    var classDef = ajax.lang.toJsonObject(className);
    if (!classDef) // Class is not defined so passed object cannot possibly inherit from it as it is not even loaded.
        return false;
    
    // attempt to get a handle to the constructor of the object we are passed
    var con = obj.constructor;
    if (!con)
        return false; // Object does not have a constructor so does not inherit from anything.
    
    // Assume the negative    
    var ret = false;
    while (con)
    {
        if (con == classDef)
            ret = true;
        con = con.superConstructor; // Step up the inheritance chain
    }        
    return ret;      
}

//-----------------------------------------------------------------------------

ajax.lang.supports = function(subClassName, baseClassName)
{///<summary>This static method allows a class to DECLARE THAT IT SUPPORTS a particular INTERFACE.</summary>
 ///<param name="subClassName" type="string">The fully qualified class name that will support an interface.</param>
 ///<param name="baseClassName" type="string">The fully qualified interface class name.</param>
 
    // Ensure that the base class (and interface definition) exists
    ajax.lang.imports(baseClassName);
    var baseClass = eval(baseClassName);
    var subClass  = eval(subClassName);
    
    // copy over the Interface (properties) to the prototype of the subClass
    ajax.lang.copyInterface(subClass, baseClass);
}
$supports = ajax.lang.supports;
ajax.lang.implementsInterface = function(subClassName, interfaceName)
{///<summary>This static method returns true if a class supports a particular interface.</summary>
    ajax.lang.imports(interfaceName);
    ajax.lang.imports(subClassName);
    var interfaceType = eval(interfaceName);
    var classType     = eval(subClassName);
    
    // check that the class has all items defined on the interface
    var result = true;
    for (var i in interfaceType.prototype)
    {
        if (!ajax.isDefined(classType.prototype[i]))
        {
            result = false;
            break;
        }
    }
    return result;
}

//-----------------------------------------------------------------------------
ajax.lang.addNamespace = $addNamespace;
function $addNamespace(sName)
{///<summary>Adds the passed name as a namespace to the system.</summary>
    var arr = sName.split(".");
    var sSpace = "";
    for (var i = 0; i < arr.length; i++)
    {
        sSpace += (i == 0) ? arr[i] : "." + arr[i];
                
        try
        {
            if (typeof(eval(sSpace)) == "undefined") 
            {
                eval(sSpace + " = {}");
                eval(sSpace + ".__namespace = true;");     
            }   
        }
        catch(ex)
        {
            eval(sSpace + " = {}");
            eval(sSpace + ".__namespace = true;");     
        }
    }
}
//-----------------------------------------------------------------------------
ajax.lang.addApplication = $addApplication;
function $addApplication(lowerCaseName, path, domain)
{///<summary>Adds a new user application to the system. </summary>
 ///<param name="lowerCaseName" type="string">The name of your application (in lower or camel case).</param>
 ///<param name="path" type="string">Optional. The path to the folder containing your client classes. Either absolute or relative to the page your program is hosted on.</param>
 ///<param name="domain" type="string">Optional. Sets the domain used to access dynamic classes. ASP.NET Ajaxna uses this for the core ajax application to point to an HTTP handler.</param>
    
    // Add a namespace for this app
    $addNamespace(lowerCaseName);
    // set the path
    eval(lowerCaseName).path = path || "";
    eval(lowerCaseName).domain = domain || "";  
    // add to the list of applications (so namespace can transform to path).
    ajax.registeredApps.push(lowerCaseName);
}
//-----------------------------------------------------------------------------



//-----------------------------------------------------------------------------
// This is an easy wrapper to get an event router from ANY object, however derived
ajax.lang.getEvent = function(wind, obj, eventType)
{
    ///<summary>Returns the existing or new event router (delegate) for the event name of the given object</summary>
    ///<returns type="ajax.events.EventRouter"></returns>
    ///<param name="wind" type="Window">A window handle</param>
    ///<param name="obj" type="Object">The object instance to publishing the event we want</param>
    ///<param name="eventType" type="String">The string name of the event we want</param>
    var ret = ajax.lang.safeCall("ajax.events.EventRouter", "getEvent", wind, obj, eventType);
    return ret;
}
var $getEvent = ajax.lang.getEvent;


//-----------------------------------------------------------------------------
// Interface support
ajax.lang.nullInterfaceFunction = function(sClassName, sInterfaceClassName, sPropName)
{
    ajax.debug.Output.writeLn(String.toFormat("The \"{0}\" class declares that it supports the \"{1}\" interface but it does not implement the \"{2}\" method or property.",
        sClassName,
        sInterfaceClassName,
        sPropName
        )
    );    
}

//-----------------------------------------------------------------------------
ajax.lang.copyInterface = function(targetClass, interfaceClass)
{            
    //for (var prop in interfaceClass)        
    for (var prop in interfaceClass.prototype)
    {                
        // Only if not already exist, pop in a debug function
        if (!targetClass.prototype[prop])       
        {            
            // Use function binding to get around closure issue with "prop" loop.
            targetClass.prototype[prop] = Function.createBinding(prop, function()
                {
                    return ajax.lang.nullInterfaceFunction(
                        targetClass.getFunctionName(),
                        interfaceClass.getFunctionName(),
                        this
                    );
                }
            );                     
        }
    }
}


//-----------------------------------------------------------------------------
ajax.lang.copyProperties = function(target, style, bPreserve, bIncludeFunctions)
{
    for (var prop in style)
        // Only if not preserved or does not already exist.
        if (!bPreserve || !target[prop])
        {
            if ( (typeof(style[prop]) == "function") && !bIncludeFunctions )
                continue;
            if ( (typeof(style[prop]) == "object") )
                ajax.lang.copyProperties(target[prop], style[prop], bPreserve, bIncludeFunctions)
            else                    
                target[prop] = style[prop];
        }                
}

//-----------------------------------------------------------------------------
// Helper function to return an array of properties from an object 
//(useful for iterating without the Object-level enhancements appearing as functions)
ajax.lang.getProperties = function(obj)
{
    var properties = new Array();    
    for (var property in obj)
    {
        var prop = obj[property];
        if (typeof(prop) == "function")
            continue;
        properties[properties.length] = prop;
    }
    return properties;
}

//-----------------------------------------------------------------------------
// A library here for simple cross browser stuff...?
ajax.lang.getDocumentWindow = function(doc)
{///<returns type="domWindow"/>
    if (!doc) // allow parameterless constructors for components.
        return window; // Pretty good guess...
        
	if (doc.parentWindow)
		return doc.parentWindow;
	else if (doc.defaultView)
		return doc.defaultView;
		
	return null;
}

//-----------------------------------------------------------------------------
ajax.lang.getQueryParams = function(doc)
{
    var E=doc.location.search;
    var F=E.substring(1,E.length).split('&');
    var AS=new Array();
    for(var i=0; i < F.length; i++)
    {
        var I=F[i].split('=');
        AS[I[0]]=I[1]
    }
    return AS;
}

//-----------------------------------------------------------------------------
ajax.lang.multiDimensionalArray = function(iRows,iCols) 
{ 
   var a = new Array(iRows); 
   for (var i=0; i < iRows; i++) 
   { 
       a[i] = new Array(iCols); 
       for (var j=0; j < iCols; j++) 
       { 
           a[i][j] = 0; 
       } 
   } 
   return(a); 
}

//-----------------------------------------------------------------------------
ajax.lang.activatePlugins = function(doc, tagNames)
{    
    var ret = 0; // Return # of replaced items
    // return if not ie or no tagNames were passed.
    if (!doc.all || !tagNames || !tagNames.length)
        return;
        
    // An array of collections (multiD array)
    var tagCollections = [];
    for (var i = 0; i < tagNames.length; i++)   
        tagCollections[tagCollections.length] = doc.getElementsByTagName(tagNames[i]);
        
    // Loop through array of collections
    for (var col = 0; col < tagCollections.length; col++)
    {    
        var collection = tagCollections[col];
        // Loop through this collection
        for (var i = 0; i < collection.length; i++)
        {
            var node = collection[i];
            var html = node.outerHTML;
            // Create new span and inserted it just before the object to be replaced.
            var span = doc.createElement("span");            
            node.parentNode.insertBefore(span, node);
            // remove the original node
            node.parentNode.removeChild(node);
            // set the deleted nodes original html value (string) as the inner HTML value of the new span tag
            span.innerHTML = html;
            ret++;
        }
    }   
    return ret;
}

var $getDesignMode = function()
{///<summary>This method always returns false at runtime. Use this method to coax intellisense into doing the right thing.</summary>
 ///<returns type="boolean"/>
    return false;
}

ajax.getDomainPath = function(suffix)
{///<summary>Gets the full Domain and Path. Use this to reference files in the Ajaxna dll that are in the Ajax folder.</summary>
 ///<param name="suffix" type="String">Optional. The suffix to add to the returned path (saves you having to concatenate it.</param>
 ///<returns type="String"/>
    suffix = (typeof(suffix) == "undefined") ? "" : suffix;
    return ajax.domain + ajax.path + suffix;
}
ajax.getDomain = function(suffix)
{///<summary>Gets the full Domain only. Use this to reference files in the Ajaxna dll that are not in the Ajax folder.</summary>
 ///<param name="suffix" type="String">Optional. The suffix to add to the returned path (saves you having to concatenate it.</param>
 ///<returns type="String"/>
    suffix = (typeof (suffix) == "undefined") ? "" : suffix;
    return ajax.domain + suffix;
}
ajax.isDefined = function(obj)
{///<summary>Returns true if the object is defined. Do not use for null tests (as null IS defined).</summary>
 ///<returns type="boolean"/>    
    return typeof(obj) != "undefined" ? true : false;
}